/*
	Pulsar engine. Core.
	Additional include file. Own vectors

	Copyright (C) 2013-2014 Dmitry Dolgov

	This program is free software; you can redistribute it and/or modify it under the terms of the
	GNU General Public License as published by the Free Software Foundation; either version 2 of
	the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
	without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
	See the GNU General Public License for more details.
*/

namespace pl
{

template<typename T> class StdVector
{
public:
	typedef T&			reference;
	typedef const T&	const_reference;
	typedef T*			iterator;
	typedef const T*	const_iterator;

	/// Ctor
	StdVector():
	_data(NULL),
	_size(0),
	_capacity(0),
	_flags(0)
	{
		return;
	}

	/// Ctor with size
	StdVector(pl_uint32 size):
	_data(NULL),
	_size(0),
	_capacity(0),
	_flags(0)
	{
		resize(size);
		return;
	}

	/// Copy ctor
	StdVector(const StdVector& vector):
	_data(NULL),
	_size(0),
	_capacity(0),
	_flags(0)
	{
		resize(vector._size);

		pl_uint32 i;
		for (i = 0; i < _size; ++i)
		{
			_data[i] = vector[i];
		}
		return;
	}

	/// Placement ctor
	StdVector(pl::StreamReadMemory& stream)
	{
		PL_ASSERTE(_size == _capacity, PL_T("Placement Save Error"));

		_flags = (FLAG_NORESIZE | FLAG_NORESERVE);
		_data = stream.restorePointer(_data);
	}

	/// dtor
	~StdVector()
	{
		if ((_flags & FLAG_NORESIZE) == 0)
		{
			pl_uint32 i;
			for (i = 0; i < _size; ++i)
			{
				(_data + i)->~T();
			}
			_size = 0;
			free_align(_data);
		}
	}

	/// Switch from placement mode to vector mode
	void switchToVector()
	{
		if (_flags & FLAG_NORESIZE)
		{
			T* data = static_cast<T*>(malloc_align(16, _size * sizeof(T)));

			pl_uint32 i;

			for (i = 0; i < _size; ++i)
			{
				new (data + i) T;
				data[i] = _data[i];
			}

			_data = data;
			_flags &= ~(FLAG_NORESIZE | FLAG_NORESERVE);
		}
	}

	/// Clear vector and switch to placement mode
	void clearAndSwitchToVector()
	{
		if (_flags & FLAG_NORESIZE)
		{
			_size = 0;
			_capacity = 0;
			_data = NULL;
			_flags &= ~(FLAG_NORESIZE | FLAG_NORESERVE);
		}
		else
		{
			clear();
		}
		return;
	}

	/// Clear vector and free allocated memory
	void clearFull()
	{
		PL_ASSERTE((_flags & FLAG_NORESIZE) == 0, PL_T("Using vector methods in Array Mode"));

		clear();

		if (_data != NULL)
		{
			free_align(_data);
		}
		_data = 0;
		_size = 0;
		_capacity = 0;
		return;
	}

	/// Disable vector resizing
	void disableReserve()
	{
		_flags |= FLAG_NORESERVE;
		return;
	}

	/// Enable vector resizing
	void enableReserve()
	{
		_flags &= ~FLAG_NORESERVE;
		return;
	}

	/// Get reference to object
	reference at(pl_uint32 pos)
	{
		PL_ASSERTE(pos < _size, PL_T("Index out of range"));
		return *(_data + pos);
	}
	/// Get const reference to object
	const_reference at(pl_uint32 pos) const
	{
		PL_ASSERTE(pos < _size, PL_T("Index out of range"));
		return *(_data + pos);
	}
	/// Get reference to last object
	reference back()
	{
		PL_ASSERTE(_size > 0, PL_T("Vector is empty"));
		return *(_data + _size - 1);
	}
	/// Get const reference to last object
	const_reference back() const
	{
		PL_ASSERTE(_size > 0, PL_T("Vector is empty"));
		return *(_data + _size - 1);
	}
	/// Get first iterator
	iterator begin()
	{
		return (_size == 0 ? 0 : _data);
	}
	/// Get first iterator (const version)
	const_iterator begin() const
	{
		return (_size == 0 ? 0 : _data);
	}
	/// Get last iterator
	iterator end()
	{
		return (_size == 0 ? 0 : _data + _size);
	}
	/// Get last iterator (const version)
	const_iterator end() const
	{
		return (_size == 0 ? 0 : _data + _size);
	}
	/// Get vector capacity
	pl_uint32 capacity() const
	{
		return _capacity;
	}
	/// Removes vector content (keep capacity)
	void clear()
	{
		PL_ASSERTE((_flags & FLAG_NORESIZE) == 0, PL_T("Using vector methods in Array Mode"));
		resize(0);
		return;
	}
	/// Check is vector empty
	bool empty() const
	{
		return (_size == 0);
	}
	/// Remove item from vector
	iterator erase(const_iterator where)
	{
		PL_ASSERTE((_flags & FLAG_NORESIZE) == 0, PL_T("Using vector methods in Array Mode"));
		PL_ASSERTE((begin() <= where) && (where < end()), PL_T("Incorrect Iterator"));

		const pl_uint32 pos = static_cast<pl_uint32>(where - begin());

		pl_uint32 i;
		for (i = pos; i < _size - 1; ++i)
		{
			_data[i] = _data[i + 1];
		}
		(_data + --_size)->~T();

		return (_size == 0 ? 0 : &_data[pos]);
	}

	/// Get reference to first item
	reference front()
	{
		PL_ASSERTE(_size > 0, PL_T("Vector is empty"));
		return *(_data);
	}
	/// Get reference to first item (const version)
	const_reference front() const
	{
		PL_ASSERTE(_size > 0, PL_T("Vector is empty"));

		return *(_data);
	}
	/// Insert new item
	iterator insert(iterator where, const T& value)
	{
		PL_ASSERTE((_flags & FLAG_NORESIZE) == 0, PL_T("Using vector methods in Array Mode"));
		PL_ASSERTE((begin() <= where) && (where <= end()), PL_T("Incorrect Iterator"));

		T copyValue = value;
		pl_int32 pos = static_cast<pl_int32>(where - begin());
		resize(_size + 1);
		pl_int32 i = _size-1;
		while ( i > pos )
		{
			_data[i] = _data[i - 1];
			i--;
		}
		_data[pos] = copyValue;
		return (_data + pos);
	}
	/// Removes last item
	void popBack()
	{
		PL_ASSERTE((_flags & FLAG_NORESIZE) == 0, PL_T("Using vector methods in Array Mode"));

		if (_size > 0)
		{
			(_data + --_size)->~T();
		}

		return;
	}
	/// Add new item
	void pushBack(const T& value)
	{
		PL_ASSERTE((_flags & FLAG_NORESIZE) == 0, PL_T("Using vector methods in Array Mode"));
		PL_ASSERTE(_size <= _capacity, PL_T("Internal Error"));

		if (_size < _capacity)
		{
			new (_data + _size++) T(value);
		}
		else
		{
			T copyValue(value);
			reserve(_capacity == 0 ? 1 : _capacity * 2);
			new (_data + _size++) T(copyValue);
		}

		return;
	}

	/// Resize the vector
	void resize(pl_uint32 size, const T& value = T())
	{
		PL_ASSERTE((_flags & FLAG_NORESIZE) == 0, PL_T("Using vector methods in Array Mode"));

		if (_capacity < size)
		{
			T copyValue(value);
			reserve(size);
			pl_uint32 i;
			for (i = _size; i < size; ++i)
			{
				new (_data + i) T(copyValue);
			}
		}
		else if (_size < size)
		{
			pl_uint32 i;
			for (i = _size; i < size; ++i)
			{
				new (_data + i) T(value);
			}
		}
		else if (size < _size )
		{
			pl_uint32 i = size;
			while (i < _size)
			{
				(_data + i++)->~T();
			}
		}

		_size = size;

		return;
	}

	/// Reserve more capacity
	void reserve(pl_uint32 count)
	{
		PL_ASSERTE((_flags & FLAG_NORESIZE) == 0, PL_T("Using vector methods in Array Mode"));

		if (_capacity < count)
		{
			PL_ASSERTE((_flags & FLAG_NORESERVE) == 0, PL_T("Using Reserve in Non Reserve Mode"));
			PL_ASSERTE(_size <= _capacity, PL_T("Internal Error"));
			PL_ASSERTE((_size == 0) || ((_size > 0) && (_data != NULL)), PL_T("Internal Error"));

			T* data = static_cast<T*>(malloc_align(16, count * sizeof(T)));
			pl_uint32 i;
			for (i = 0; i < _size; ++i)
			{
				new (data + i) T(_data[i]);
				(_data + i)->~T();
			}
			free_align(_data);

			_capacity = count;
			_data = data;
		}

		return;
	}

	/// Get item count
	pl_uint32 size() const
	{
		return _size;
	}
	/// Swap two vectors
	void swap(StdVector& vector)
	{
		PL_ASSERTE(((_flags & FLAG_NORESIZE) == 0) && ((vector._flags & FLAG_NORESIZE) == 0), PL_T("Using vector methods in Array Mode"));

		T* data = _data;
		_data = vector._data;
		vector._data = data;

		const pl_uint32 size = _size;
		_size = vector._size;
		vector._size = size;

		const pl_uint32 capacity = _capacity;
		_capacity = vector._capacity;
		vector._capacity = capacity;

		return;
	}

	/// Sort items
	void sort(int (__cdecl *compare)(const void *, const void *))
	{
		qsort(_data, (size_t)_size, sizeof(T), compare);
	}

	/// Get reference to item
	reference operator[](pl_uint32 pos)
	{
		PL_ASSERTE(pos < _size, PL_T("Index out of range"));
		return *(_data + pos);
	}
	/// Get reference to item (const version)
	const_reference operator[](pl_uint32 pos) const
	{
		PL_ASSERTE(pos < _size, PL_T("Index out of range"));
		return *(_data + pos);
	}

	/// Set
	StdVector& operator=(const StdVector& vector)
	{
		PL_ASSERTE((_flags & FLAG_NORESIZE) == 0, PL_T("Using vector methods in Array Mode"));

		if (this == &vector)
		{
			;
		}
//		else if (vector.size() <= _size)
//		{
//		}
//		else if (vector.size() <= _capacity)
//		{
//		}
		else
		{
			resize(vector.size());

			pl_uint32 i;
			for (i = 0; i < _size; ++i)
			{
				_data[i] = vector[i];
			}
		}

		return (*this);
	}

	/// Find an item
	pl_uint32 find(const T& ref) const
	{
		pl_uint32 i;
		for (i = 0; i < _size; ++i)
		{
			if (_data[i] == ref)
			{
				return i;
			}
		}
		return (pl_uint32)-1;
	}

private:
	T* _data;
	pl_uint32 _size;
	pl_uint32 _capacity;
	pl_uint32 _flags;

	enum
	{
		FLAG_NORESIZE  = 0x00000001, // Vector was load from placement file and can't be resized
		FLAG_NORESERVE = 0x00000002  // Change capacity is prohibited
	};
};

} // namespace pl

//
// End of file 'pulsar_vector.h'
//
